/**
 * Copyright 2009-2010 - YangJiandong(chunquedong)
 * 
 * This file is part of ChunMap project
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE(Version >=3)
 * 你可以自由复制、传播本项目的下载包文件，但必须保持其完整性。
 * 我们不用对使用中的风险及由此造成的损失承担任何责任。
 * 详细情况请见《ChunMap许可协议》。

 * 想了解更多有关ChunMap的信息，请访问http://code.google.com/p/chunmap/
 */
package chunmap.model.elem;

import java.util.ArrayList;
import java.util.List;

import chunmap.model.algorithm.LineSegmentAlgorithm;
import chunmap.model.coord.Coordinate2D;
import chunmap.model.coord.Position;
import chunmap.model.geom.LineString;
import chunmap.util.math.MyDouble;

/**
 * 线段
 * 
 * @author chunquedong
 * 
 */
public class LineSegment {

	private final Position startPoint;
	private final Position endPoint;

	//------------------------------------------------------------------------getter
	/**
	 * @param 起点
	 * @param 终点
	 */
	public LineSegment(Position startPoint, Position endPoint) {
		if (startPoint.equals(endPoint))
			throw new IllegalArgumentException(
					"need tow different points to make one line");
		this.startPoint = startPoint;
		this.endPoint = endPoint;
	}

	public Position getStartPoint() {
		return startPoint;
	}

	public Position getEndPoint() {
		return endPoint;
	}

	public double getDistance() {
		double x1 = startPoint.getX();
		double y1 = startPoint.getY();
		double x2 = endPoint.getX();
		double y2 = endPoint.getY();

		double d1 = Math.pow((x1 - x2), 2.0);
		double d2 = Math.pow((y1 - y2), 2.0);

		double dis = Math.sqrt(d1 + d2);
		return dis;
	}

	public double getDistance3D() {
		double x1 = startPoint.getX();
		double y1 = startPoint.getY();
		double z1 = startPoint.getZ();
		double x2 = endPoint.getX();
		double y2 = endPoint.getY();
		double z2 = endPoint.getZ();

		double d1 = Math.pow((x1 - x2), 2.0);
		double d2 = Math.pow((y1 - y2), 2.0);
		double d3 = Math.pow((z1 - z2), 2.0);

		double dis = Math.sqrt(d1 + d2 + d3);
		return dis;
	}

	public double taxiDistance() {
		double x1 = startPoint.getX();
		double y1 = startPoint.getY();
		double x2 = endPoint.getX();
		double y2 = endPoint.getY();

		return Math.abs(x1 - x2) + Math.abs(y1 - y2);
	}

	public Envelope getEnvelop() {
		return new Envelope(startPoint, endPoint);
	}

	public Position getMiddlePoint() {
		double x = (startPoint.getX() + endPoint.getX()) / 2d;
		double y = (startPoint.getY() + endPoint.getY()) / 2d;
		return new Coordinate2D(x, y);
	}
	
	//------------------------------------------------------------------------Intersect
	
	public boolean hasIntersect(LineSegment other) {
		if (!this.getEnvelop().hasIntersect(other.getEnvelop()))
			return false;
		if (this.kuaLi(other) && other.kuaLi(this))
			return true;
		return false;
	}

	/**
	 * 计算两点交集，没有交集是返回null，交集可能是Point,PointArray
	 * 
	 * @param other
	 * @return
	 */
	public Object intersection(LineSegment other) {
		Object r=LineSegmentAlgorithm.intersection(this, other);
        return r;
	}

	/**
	 * 是否跨立另一条线
	 * 
	 * @param other
	 * @return
	 */
	private boolean kuaLi(LineSegment other) {
		Vector v1 = new Vector(this.startPoint,other.startPoint);
		Vector v2 = new Vector(other.endPoint, other.startPoint);
		Vector v3 = new Vector(this.endPoint, other.startPoint);

		double ji1 = v1.get2DCrossProduct(v2);
		double ji2 = v3.get2DCrossProduct(v2);

		double he = ji1 * ji2;
		return he <= 0;
	}

	//------------------------------------------------------------------------util
	
	/**
	 * 点是否在线段上
	 * 
	 * @param p
	 * @return
	 */
	public boolean onLineSegment(Position p) {
		if (!this.getEnvelop().contain(p))
			return false;

		if (onBorder(p))
			return true;

		Vector v1 = new Vector(p, startPoint);
		Vector v2 = new Vector(endPoint, startPoint);
		double ji = v1.get2DCrossProduct(v2);
		if (MyDouble.approximateEquals(ji, 0))
			return true;
		return false;
	}

	public boolean onBorder(Position p) {
		if (p.equals(startPoint) || p.equals(endPoint))
			return true;
		return false;
	}

	/**
	 * 包含另一条线吗
	 * 
	 * @param lseg2
	 * @return
	 */
	public boolean containLineSegment(LineSegment lseg2) {

		boolean hasI = this.getEnvelop().hasIntersect(lseg2.getEnvelop());
		if (!hasI)
			return false;

		if (this.onLineSegment(lseg2.getStartPoint())
				&& this.onLineSegment(lseg2.getEndPoint())) {
			return true;
		}

		return false;
	}

	public boolean equalsIgnoreOrder(LineSegment lseg) {
		if (this.equals(lseg))
			return true;
		if (this.startPoint.equals(lseg.endPoint)
				&& this.endPoint.equals(lseg.startPoint))
			return true;
		return false;
	}

	public Line toLine() {
		return new Line(startPoint, endPoint);
	}

	public Vector toVector() {
		return new Vector(startPoint, endPoint);
	}

	public LineString toLineString() {
		List<Position> pointList = new ArrayList<Position>();
		pointList.add(startPoint);
		pointList.add(endPoint);
		return new LineString(pointList);
	}

	/**
	 * 线段的定比分点
	 * 
	 * @param lamuda
	 * @return
	 */
	public Position dingBiFenDian(double lamuda) {
		if (lamuda == -1)
			new IllegalArgumentException();
		double x = (startPoint.getX() + endPoint.getX() * lamuda)
				/ (1 + lamuda);
		double y = (startPoint.getY() + endPoint.getY() * lamuda)
				/ (1 + lamuda);
	
		return new Coordinate2D(x, y);
	}

	@Override
	public String toString() {
		return "LineSegment [startPoint=" + startPoint + ",endPoint="
				+ endPoint + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((endPoint == null) ? 0 : endPoint.hashCode());
		result = prime * result
				+ ((startPoint == null) ? 0 : startPoint.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		LineSegment other = (LineSegment) obj;
		if (endPoint == null) {
			if (other.endPoint != null)
				return false;
		} else if (!endPoint.equals(other.endPoint))
			return false;
		if (startPoint == null) {
			if (other.startPoint != null)
				return false;
		} else if (!startPoint.equals(other.startPoint))
			return false;
		return true;
	}

}
